針對資料庫內的資料進行查詢、新增、刪修都需要迅速地找到該筆資料,因此建立索引很重要。至於要如何評估指令的效能如何,例如參數設計、順序,就需要使用 MongoDB 的 explain
指令,其他資料庫如 Oracle, MSSQL 叫做 execution plan
。
而 MongoDB 使用語法很簡單
db.collection.find().explain()
MongoDB 的 explain 總共有三種模式,分別是:
此模式下,查詢語法會透過內建的 query optimizer
選出最佳的查詢計畫,並且 評估
查詢結果,同時也會列出那些較差的查詢計畫。
無論是查詢或者增刪修,都不會實際修改資料庫的值。
若沒有設定 verbose,此模式為預設模式。
此模式下,會根據上述的最佳計畫執行,無論是查詢或增刪修,都會去執行並且取得結果,但是不會真的去改變資料庫的值,這樣做目的當然是告訴你執行的效率如何。
基本上就是包含上述兩者。
其實 explain 功能以及呈現內容一直都隨著改版增加,所以你看到的輸出結果不見得會跟網路上其他文章一樣,不過觀念上都是一樣的,記得這點即可。
語法上有兩種方式,像是上面介紹的一種。
在參數方別帶入想要執行的模式 (queryPlanner, executionStats, allPlansExecution)
db.collection.find().explain()
db.collection.find().explain("queryPlanner")
db.collection.find().explain("executionStats")
db.collection.find().explain("allPlansExecution")
或者啟用 verbosity,預設模式就會改為 "allPlansExecution" (但還是可以修改),總之就是看哪個順手了。
db.runCommand(
{
explain: { querySyntax },
})
or
db.runCommand(
{
explain: { count: "employee", query: { age: { $gte: 30 } } },
verbosity: "executionStats"
})
我們先來看看之前文章的範例資料庫中,沒有建立任何 index 情況下,不帶條件查詢的結果如何。
db.employee.find().explain()
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "hellomongodb.employee", // 查詢的 db 與 collection
"indexFilterSet" : false, // 後面再講..
"parsedQuery" : {}, // 查詢條件
"winningPlan" : { // 勝出的查詢計畫
"stage" : "COLLSCAN", // 關鍵參數,查詢的使用方式
"direction" : "forward"
},
"rejectedPlans" : []
},
"serverInfo" : {
"host" : "ApieMacbook.local",
"port" : 27666,
"version" : "5.0.3",
"gitVersion" : "7ea530946fa7880364d88c8d8b6026bbc9ffa48c"
},
"ok" : 1.0
}
serverInfo
屬於一些固定資訊,之後就不再貼上來佔版面。
暫時不解釋差異,我們先替 employee collection 建立以 name
欄位的 index
db.employee.createIndex( { name: 1 } )
再準備第二個查詢語法以及執行計畫,查詢 name 為 Devil
db.employee.find({"name":"Devil"}).explain()
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "hellomongodb.employee",
"indexFilterSet" : false,
"parsedQuery" : {
"name" : {
"$eq" : "Devil"
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"name" : 1.0
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"Devil\", \"Devil\"]"
]
}
}
},
"rejectedPlans" : []
},
parsedQuery
就是你的查詢條件winningPlan
系統選出的查詢,在剛開始不帶任何條件時,結果是這樣"winningPlan" : {
"stage" : "COLLSCAN",
"direction" : "forward"
},
而使用了索引去查詢,會有這樣結果
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"name" : 1.0
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"Devil\", \"Devil\"]"
]
}}}
stage
從 COLLSCAN 變成 IXSCAN。
Stage 有以下幾種:
COLLSCAN
掃描整個 CollectionIXSCAN
根據 Index 進行掃描FETCH
根據 Index 進行掃描資料(Document)SHARD_MERGE
合併各分片(shards)取得的資料SHARDING_FILTER
for filtering out orphan documents from shards (這邊我直接使用官方的說明,之後再補上情境)基本上看到 COLLSCAN
就是完全禁止的,代表你的查詢參數、語法沒辦法有效率的取得資料,原本幾毫秒的查詢可能會變成秒級以上。
所以定期的檢驗所有查詢,看是否有 COLLSCAN 是非常重要的。MongoDB 內建的 profiler 也有相關功能,有興趣也可以找一下之前寫的文章。MongoDB Atlas 也有功能,但沒直接指出來是哪個查詢,有點可惜。
本篇講解了如何使用 explain
指令去分析你的查詢語法,基本上已經非常足夠使用在大部分的索引設計和使用情境,一定要確保在開需求時,使用情境有符合資料庫的設計,否則實作下去了,會受限於技術設計而影響到使用者情境。
下一篇文章再開始講 executionStats
內的項目。
本系列文章會同步發表於我個人的部落格 Pie Note